package top.lyjwn.todo.weinxin.bjnews.service;

import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.lyjwn.todo.weinxin.api.bjnews.RefreshUserAccessToken;
import top.lyjwn.todo.weinxin.data.bjnews.BjnewsUserAccessToken;
import top.lyjwn.todo.weinxin.data.error.WxError;
import top.lyjwn.todo.weinxin.exception.WXBBaseException;
import top.lyjwn.todo.weinxin.exception.WXBUserException;

import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 微信公众号_用户_服务
 *
 * @author luoyujie
 */
@Component
public class WXBUserService implements Runnable {

    private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();


    private final Logger log = LoggerFactory.getLogger("error");
    /**
     * ACCESS_TOKEN 缓存池的最大条数
     */
    private static int ACCESS_TOKEN_POOL_SIZE = 1000;
    /**
     * 缓存得最久的AccessToken，refreshToken过期时间最近
     */
    private static String old_refresh_token;

    /**
     * ACCESS_TOKEN 缓存最大过期时间段，由于微信返回的是一个变量expires_in，可能在运行中会变化
     */
    public static long MAX_EXPIRES = 7200 * 1000;

    /**
     * AccessToken缓存池
     */
    private static Map<String, BjnewsUserAccessToken> ACCESS_TOKEN_POOL = new ConcurrentHashMap<String, BjnewsUserAccessToken>();

    @Autowired
    private RefreshUserAccessToken refreshUserAccessToken;

    /**
     * 清理refreshToken过期的AccessToken,找出最久的AccessToken,刷新过期的AccessToken
     *
     * @throws WXBBaseException
     */
    public void work() throws WXBUserException {
        Set<String> keySet = ACCESS_TOKEN_POOL.keySet();
        for (String refresh_token : keySet) {
            BjnewsUserAccessToken accessToken = ACCESS_TOKEN_POOL.get(refresh_token);
            if (accessToken.getExpiresTime() <= (new Date()).getTime()) {
                ACCESS_TOKEN_POOL.remove(refresh_token);
            }
            // 找出最老的AccessToken
            BjnewsUserAccessToken accessToken2 = ACCESS_TOKEN_POOL.get(refresh_token);
            if (old_refresh_token == null
                    || accessToken2.getExpiresTime() > ACCESS_TOKEN_POOL.get(old_refresh_token).getExpiresTime()) {
                old_refresh_token = refresh_token;
            }
            // 过期的则重新获取
            if (accessToken2.getExpiresInTime() < ACCESS_TOKEN_POOL.get(old_refresh_token).getExpiresTime()) {
                BjnewsUserAccessToken accessToken3 = refreshToken(refresh_token);
                ACCESS_TOKEN_POOL.put(refresh_token, accessToken3);
            }
            // 最大过期时间
            if (accessToken2.getExpiresIn() > MAX_EXPIRES) {
                MAX_EXPIRES = accessToken2.getExpiresIn() * 1000;
            }
        }
    }

    /**
     * 写入accessToken
     *
     * @param refresh_token
     * @param accessToken
     */
    public static void put(String refresh_token, BjnewsUserAccessToken accessToken) {
        if (ACCESS_TOKEN_POOL.size() >= ACCESS_TOKEN_POOL_SIZE) {
            ACCESS_TOKEN_POOL.remove(old_refresh_token);
            ACCESS_TOKEN_POOL.put(refresh_token, accessToken);
        } else {
            ACCESS_TOKEN_POOL.put(refresh_token, accessToken);
        }
    }

    /**
     * 根据 refresh_token 获得 accessToken
     *
     * @param refresh_token
     * @return
     */
    public static BjnewsUserAccessToken get(String refresh_token) {
        return ACCESS_TOKEN_POOL.get(refresh_token);
    }

    /**
     * 刷新AccessToken
     *
     * @param refreshToken
     * @return
     * @throws WXBUserException
     */
    private BjnewsUserAccessToken refreshToken(String refreshToken) throws WXBUserException {
        JSONObject resultJSONObject = this.refreshUserAccessToken.getResultJSONObject(refreshToken);
        BjnewsUserAccessToken accessToken = BjnewsUserAccessToken.builder(resultJSONObject);
        if (accessToken.getErrcode().equals("0")) {

            throw new WXBUserException(accessToken.getErrmsg(),
                    new WxError(accessToken.getErrcode(), accessToken.getErrmsg()));
        }
        return BjnewsUserAccessToken.builder(resultJSONObject);
    }

    @Override
    public void run() {
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    work();
                } catch (WXBUserException e1) {
                    log.error(e1.getMessage());
                }
            }
        }, 10 * 1000, MAX_EXPIRES, TimeUnit.MILLISECONDS);

    }
}
